package de.koller.oceanFramework.plain;

import de.koller.oceanFramework.OceanException;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.io.PrintStream;
import javax.swing.JFrame;

/**
 * 
 * @author Fabian Koller
 */
public class PlainOceanGame extends Canvas {
	private static final long serialVersionUID = 1L;
	
	private final PlainGameRoot root;
	private boolean running = true;
	
	private JFrame container = null;
	private BufferStrategy strategy;
	
	private GraphicsDevice graphicsDevice;
	private DisplayMode displayMode;
	private final DisplayMode oldDisplayMode;
	private boolean fullscreen = false;
	
	private final int width, height;
	private final boolean unlimitedFrame;
	private final Container panel;
	
	private double secondsPerTick = 1d / 60d;
	
	/**
	 * 
	 * @param width
	 * @param height
	 * @param unlimitedFrames
	 * @param pane
	 */
	public PlainOceanGame( int width, int height, boolean unlimitedFrames, Container pane ) {
		this.unlimitedFrame = unlimitedFrames;
		this.root = new PlainGameRoot( this );
		
		if( pane == null )
			container = new JFrame();
		
		panel = pane == null ? container.getContentPane() : pane;
		
		panel.setPreferredSize( new Dimension( width, height) );
		panel.setLayout( null );
		
		this.setBounds( 0, 0, width, height );
		panel.add( this );
		this.setIgnoreRepaint( true );
		
		if( container != null ) {
			container.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
			container.pack();
			container.setResizable( false );
			
			container.addWindowListener( new WindowAdapter() {
				@Override public void windowClosing( WindowEvent e ) {
					stop();
				}
			} );
		}
		
		this.width = width;
		this.height = height;

		graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		oldDisplayMode = graphicsDevice.getDisplayMode();
	}
	
	/**
	 * 
	 * @param width
	 * @param height
	 * @param unlimitedFrames
	 */
	public PlainOceanGame( int width, int height, boolean unlimitedFrames ) {
		this( width, height, unlimitedFrames, null );
	}
	
	private void setDisplayMode( int width, int height, int depth, int frequenz ) {
		DisplayMode[] dms = graphicsDevice.getDisplayModes();
		
		for( DisplayMode dm : dms )
			if( dm.getWidth() == width && dm.getHeight() == height && 
					dm.getBitDepth() == depth && dm.getRefreshRate() == frequenz ) {
				displayMode = dm;
				return;
			}
		
		throw new OceanException( "Could not find required Display Mode!" );
	}
 	
	/**
	 * 
	 */
	public void setFullscreen() {
		setDisplayMode( width, height, 32, 60 );
		fullscreen = true;
		
		graphicsDevice.setFullScreenWindow( container );
		graphicsDevice.setDisplayMode( displayMode );
	}
	
	/**
	 * 
	 */
	public void start() {
		if( container != null ) {
			container.setVisible( true );
			container.pack();

			if( !fullscreen )
				container.setLocation( (int)(Toolkit.getDefaultToolkit().getScreenSize().getWidth()/2d - container.getWidth()/2d ),
						(int)(Toolkit.getDefaultToolkit().getScreenSize().getHeight()/2d - container.getHeight()/2d ) );
		}
		
		this.createBufferStrategy( 3 );
		strategy = this.getBufferStrategy();
		
		this.requestFocus();
		
		root.initalize();
		
		if( unlimitedFrame )
			unlimitedLoop();
		else
			limitedLoop();
		
		root.releasing();
		
		if( fullscreen ) {
			graphicsDevice.setDisplayMode( oldDisplayMode );
			graphicsDevice.setFullScreenWindow( null );
		}
		
		container.dispose();
//		System.exit( 0 );
	}
	
	private void limitedLoop() {
		int frames = 0;
		
		double unprocessedSeconds = 0;
		long lastTime = System.nanoTime();
		int tickCount = 0;

		long timePerLoop = 0;
//		long loops = 0;
		
		while( running ) {
			long now = System.nanoTime();
			long passedTime = now - lastTime;
//			timePerLoop += passedTime;
//			loops++;
					
			lastTime = now;
			
			if( passedTime < 0 ) passedTime = 0;
			if( passedTime > 100_000_000 ) passedTime = 100_000_000;
			
			unprocessedSeconds += passedTime / 1000_000_000d;
			
//			boolean ticked = false;
			if( unprocessedSeconds > secondsPerTick ) {
				long independetTime = System.nanoTime();
				
				update( (long)(secondsPerTick * 1000) );
				unprocessedSeconds -= secondsPerTick;
//				unprocessedSeconds = 0;
				
				render( (long)(secondsPerTick * 1000) );
				frames++;
				
				timePerLoop += System.nanoTime() - independetTime;
//				ticked = true;
				
				tickCount++;
				if( tickCount % 100 == 0 ) {
//					double tpl = ( (double)timePerLoop/(double)loops / 1_000_000d);
					double tpl = ( (double)timePerLoop / 100d / 1_000_000d);
					double per = Math.round( tpl / (secondsPerTick*1000d) * 10d ) / 10d * 100d;
					
					PrintStream ps;
					if( per > 99 )
						ps = System.err;
					else
						ps = System.out;
						
					ps.println( frames + " frames (" + (Math.round( tpl*100 )/100d) + " ms/frame = " + per + "% "
							+ "childs: " + root.countChilds() + ")" );
					
					timePerLoop = 0;
//					loops = 0;
					
					lastTime += 1000;
					frames = 0;
				}
			}
			
//			if( ticked ) {
//				render( (long)(secondsPerTick * 1000) );
//				frames++;
//			} //else try {
//				Thread.sleep( 1 );
//			} catch( InterruptedException e ) {
//				e.printStackTrace();
//			} 
		}
	}
	
	private void unlimitedLoop() {
		long lastLoopTime = System.currentTimeMillis();
		while( running ) {
			long delta = System.currentTimeMillis() - lastLoopTime;
			lastLoopTime = System.currentTimeMillis();
			
			update( delta );
			render( delta );
		}
	}
	
	private void update( long delta ) {
		root.updating( (int)delta );
	}
	
	private void render( long delta ) {
		Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
		g.setColor( Color.black );
		g.fillRect( 0, 0, width, height );

		root.setGraphics( g );
		root.rendering( (int)delta );

		g.dispose();
		strategy.show();
	}
	
	/**
	 * 
	 */
	public void stop() {
		running = false;
	}

	/**
	 * 
	 * @return
	 */
	public PlainGameRoot getRoot() {
		return root;
	}

	/**
	 * 
	 * @param title
	 */
	public void setTitle( String title ) {
		if( container != null )
			container.setTitle( title );
	}

	/**
	 * 
	 * @return
	 */
	public Container getPanel() {
		return panel;
	}

	/**
	 * 
	 * @return
	 */
	public JFrame getContainer() {
		return container;
	}

	public double getSecondsPerTick() {
		return secondsPerTick;
	}

	public void setSecondsPerTick( double secondsPerTick ) {
		this.secondsPerTick = secondsPerTick;
	}
	
}
